﻿using System;
using System.Collections.Generic;

namespace MiniFox.Caching
{
    /// <summary>
    /// 
    /// </summary>
    public abstract class CacheProvider
    {
        private int _defaultExpireSeconds;
        private readonly static object __lock;
        private string _keyPrefix;
        private string _keySuffix;

        static CacheProvider()
        {
            __lock = new object();
        }
        /// <summary>
        /// 
        /// </summary>
        protected CacheProvider() : this(3600)
        {
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="defaultExpireSeconds"></param>
        protected CacheProvider(int defaultExpireSeconds) : this(string.Empty, string.Empty, defaultExpireSeconds)
        {

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="keyPrefix"></param>
        /// <param name="keySuffix"></param>
        /// <param name="defaultExpireSeconds"></param>
        protected CacheProvider(string keyPrefix, string keySuffix, int defaultExpireSeconds)
        {
            _defaultExpireSeconds = defaultExpireSeconds;
            _keyPrefix = keyPrefix;
            _keySuffix = keySuffix;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        protected virtual string GenerateKeyWithFormat(string key)
        {
            if (string.IsNullOrEmpty(this.KeyPrefix) && string.IsNullOrEmpty(this.KeySuffix))
                return key;
            if (string.IsNullOrEmpty(this.KeySuffix))
                return $"{this.KeyPrefix}.{key}";
            if (string.IsNullOrEmpty(this.KeyPrefix))
                return $"{key}.{this.KeySuffix}";
            return $"{this.KeyPrefix}.{key}.{this.KeySuffix}";
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public void Add(string key, object value)
        {
            Add(key, value, this.DefaultExpireSeconds);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="durationSeconds"></param>
        public void Add(string key, object value, int durationSeconds) 
        {
            Add(key, value, TimeSpan.FromSeconds(durationSeconds));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="timeSpan"></param>
        public virtual void Add(string key, object value, TimeSpan timeSpan)
        {
            AddItem(this.GenerateKeyWithFormat(key), value, timeSpan);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="timeSpan"></param>
        protected abstract void AddItem(string key, object value, TimeSpan timeSpan);
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public virtual object Get(string key)
        {
            key = this.GenerateKeyWithFormat(key);
            return GetItem(key);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public object Get(string key, Func<object> func)
        {
            return Get(key, func, this.DefaultExpireSeconds);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="func"></param>
        /// <param name="durationSeconds"></param>
        /// <returns></returns>
        public object Get(string key, Func<object> func, int durationSeconds)
        {
            return Get(key, func, TimeSpan.FromSeconds(durationSeconds));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="func"></param>
        /// <param name="timeSpan"></param>
        /// <returns></returns>
        public object Get(string key, Func<object> func, TimeSpan timeSpan)
        {
            var obj = Get(key);
            if (obj == null)
            {
                lock (__lock)
                {
                    obj = func();
                    this.Add(key, obj, timeSpan);
                }
            }
            return obj;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public virtual T Get<T>(string key)
        {
            object val = Get(key);
            return Convert<T>(val);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public T Get<T>(string key, Func<T> func)
        {
            return Get(key, func, this.DefaultExpireSeconds);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="func"></param>
        /// <param name="durationSeconds"></param>
        /// <returns></returns>
        public T Get<T>(string key, Func<T> func, int durationSeconds)
        {
            return Get(key, func, TimeSpan.FromSeconds(durationSeconds));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="func"></param>
        /// <param name="timeSpan"></param>
        /// <returns></returns>
        public T Get<T>(string key, Func<T> func, TimeSpan timeSpan)
        {
            return Get(key, func, timeSpan, false);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="func"></param>
        /// <param name="ignoreOnError"></param>
        /// <returns></returns>
        public T Get<T>(string key, Func<T> func, bool ignoreOnError)
        {
            return Get(key, func, this.DefaultExpireSeconds, ignoreOnError);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="func"></param>
        /// <param name="durationSeconds"></param>
        /// <param name="ignoreOnError"></param>
        /// <returns></returns>
        public T Get<T>(string key, Func<T> func, int durationSeconds, bool ignoreOnError)
        {
            return Get(key, func, TimeSpan.FromSeconds(durationSeconds), ignoreOnError);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="func"></param>
        /// <param name="timeSpan"></param>
        /// <param name="ignoreOnError"></param>
        /// <returns></returns>
        public T Get<T>(string key, Func<T> func, TimeSpan timeSpan, bool ignoreOnError)
        {
            lock (__lock)
            {
                if (this.Contains(key))
                {
                    return Get<T>(key);
                }
                else
                {
                    try
                    {
                        T obj = func();
                        if (obj != null)
                        {
                            this.Add(key, obj, timeSpan);
                            return obj;
                        }
                        else
                        {
                            return default;
                        }
                    }
                    catch (Exception e)
                    {
                        if (ignoreOnError)
                            return default;
                        else
                            throw e;
                    }
                }
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        protected abstract object GetItem(string key);
        /// <summary>
        /// 
        /// </summary>
        /// <param name="keys"></param>
        /// <returns></returns>
        public virtual IDictionary<string, object> Get(params string[] keys)
        {
            IDictionary<string, object> keyVal = new Dictionary<string, object>();
            foreach (string key in keys)
            {
                keyVal.Add(key, this.Get(key));
            }
            return keyVal;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        public virtual void Remove(string key)
        {
            RemoveItem(this.GenerateKeyWithFormat(key));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        protected abstract void RemoveItem(string key);
        /// <summary>
        /// 
        /// </summary>
        /// <param name="keys"></param>
        public virtual void Remove(params string[] keys)
        {
            foreach(string key in keys)
                Remove(key);
        }
        /// <summary>
        /// 
        /// </summary>
        public abstract void RemoveAll();
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public virtual bool Contains(string key)
        {
            return ContainItem(this.GenerateKeyWithFormat(key));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        protected abstract bool ContainItem(string key);
        /// <summary>
        /// 
        /// </summary>
        public virtual int DefaultExpireSeconds
        {
            get { return this._defaultExpireSeconds; }
        }
        /// <summary>
        /// 
        /// </summary>
        public virtual string KeyPrefix
        {
            get { return this._keyPrefix; }
        }
        /// <summary>
        /// 
        /// </summary>
        public virtual string KeySuffix
        {
            get { return this._keySuffix; }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="val"></param>
        /// <returns></returns>
        protected virtual T Convert<T>(object val)
        {
            return (T)val;
        }
    }
}
